package com.chen.shopping_search_service.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.FieldSuggester;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
import co.elastic.clients.elasticsearch.indices.AnalyzeResponse;
import co.elastic.clients.elasticsearch.indices.analyze.AnalyzeToken;
import co.elastic.clients.json.JsonData;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.chen.shopping_common.pojo.*;
import com.chen.shopping_common.service.GoodsService;
import com.chen.shopping_common.service.SearchService;
import com.chen.shopping_search_service.repository.GoodsESRepository;
import lombok.SneakyThrows;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;

@DubboService
public class SearchServiceImpl implements SearchService {


    @Autowired
    private ElasticsearchClient elasticsearchClient;
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    @Autowired
    private GoodsESRepository goodsESRepository;
    /**
     *
     * @param text 输入的文本
     * @param analyzer 分词器
     * @return
     *
     */
    @SneakyThrows
    public List<String> analyze(String text,String analyzer){
        //创建分词请求
        AnalyzeRequest analyzeRequest = AnalyzeRequest.of(a -> a.index("goods").analyzer(analyzer).text(text));
        //发送分词请求
        AnalyzeResponse analyze = elasticsearchClient.indices().analyze(analyzeRequest);
        //处理分词结果
        List<String> words = new ArrayList<>();
        List<AnalyzeToken> tokens = analyze.tokens();
        for (AnalyzeToken token : tokens) {
            words.add(token.token());
        }
        return words;
    }

    /**
     * 自动补齐关键字
     * @param keyword 被补齐的词
     * @return 补齐后的关键词集合
     */
    @SneakyThrows
    @Override
    public List<String> autoSuggest(String keyword) {
        //自动补齐查询条件
        Suggester suggester = Suggester.of(
                s -> s.suggesters("prefix_suggestion", FieldSuggester.of(
                        fs -> fs.completion(
                                cs -> cs.skipDuplicates(true)
                                        .size(10)
                                        .field("tags")
                        )
                )).text(keyword)
        );

        //自动补齐查询
        SearchResponse<Map> response = elasticsearchClient.search(s -> s.index("goods").suggest(suggester), Map.class);
        //处理查询结果
        Map<String, List<Suggestion<Map>>> resultMap = response.suggest();
        List<Suggestion<Map>> suggestionList = resultMap.get("prefix_suggestion");
        Suggestion<Map> mapSuggestion = suggestionList.get(0);
        List<CompletionSuggestOption<Map>> resultList = mapSuggestion.completion().options();

        List<String> result = new ArrayList<>();
        for (CompletionSuggestOption<Map> mapCompletionSuggestOption : resultList) {
            String text = mapCompletionSuggestOption.text();
            result.add(text);
        }
        return result;
    }

    /**
     * 搜索商品
     * @param goodsSearchParam 搜索条件
     * @return 搜索结果
     */
    @Override
    public GoodsSearchResult search(GoodsSearchParam goodsSearchParam) {
        // 1.构造ES查询条件
        NativeQuery nativeQuery = buildQuery(goodsSearchParam);
        // 2.搜索
        SearchHits<GoodsES> search = elasticsearchTemplate.search(nativeQuery, GoodsES.class);
        // 3.将查询结果封装为Mybatis-plus的Page对象
        // 3.1 将searchHit转换为list
        List<GoodsES> list = new ArrayList<>();
        for (SearchHit<GoodsES> goodsESSearchHit : search) {
            GoodsES content = goodsESSearchHit.getContent();
            list.add(content);
        }
        // 3.2 将list转化为Mybatis-plus的Page对象
        Page<GoodsES> page = new Page();
        page.setCurrent(goodsSearchParam.getPage())
                .setSize(goodsSearchParam.getSize())
                .setTotal(search.getTotalHits())
                .setRecords(list);
        // 4.封装查询结果
        GoodsSearchResult goodsSearchResult = new GoodsSearchResult();
        // 4.1 封装商品
        goodsSearchResult.setGoodsPage(page);
        // 4.2 封装查询参数
        goodsSearchResult.setGoodsSearchParam(goodsSearchParam);
        // 4.3 封装查询面板
        buildSearchPanel(goodsSearchParam,goodsSearchResult);
        return goodsSearchResult;
    }

    public void buildSearchPanel(GoodsSearchParam goodsSearchParam , GoodsSearchResult goodsSearchResult){
        //构造查询条件
        goodsSearchParam.setPage(1);
        goodsSearchParam.setSize(20);
        goodsSearchParam.setSort(null);
        goodsSearchParam.setSortFiled(null);
        NativeQuery nativeQuery = buildQuery(goodsSearchParam);
        //搜索
        SearchHits<GoodsES> search = elasticsearchTemplate.search(nativeQuery, GoodsES.class);
        //将结果封装为List对象
        List<GoodsES> list = new ArrayList<>();
        for (SearchHit<GoodsES> goodsESSearchHit : search) {
            GoodsES content = goodsESSearchHit.getContent();
            list.add(content);
        }
        //遍历集合，封装查询面板
        Set<String> brands = new HashSet<>();
        Set<String> productTypes = new HashSet<>();
        Map<String,Set<String>> specifications = new HashMap<>();

        for (GoodsES goodsES : list) {

            brands.add(goodsES.getBrand());
            productTypes.addAll(goodsES.getProductType());

            Map<String, List<String>> specification = goodsES.getSpecification();
            Set<Map.Entry<String, List<String>>> entries = specification.entrySet();
            for (Map.Entry<String, List<String>> entry : entries) {
                String key = entry.getKey();
                List<String> value = entry.getValue();
                if (specifications.containsKey(key)){
                    specifications.get(key).addAll(value);
                }else {
                    specifications.put(key,new HashSet(value));
                }
            }
        }
        goodsSearchResult.setBrands(brands);
        goodsSearchResult.setProductType(productTypes);
        goodsSearchResult.setSpecifications(specifications);
    }

    /**
     * 构造搜索条件
     * @param goodsSearchParam 查询条件对象
     * @return 封装好的搜索条件
     */
    private NativeQuery buildQuery(GoodsSearchParam goodsSearchParam){
        // 1.创建复杂查询条件对象
        NativeQueryBuilder nativeQueryBuilder = new NativeQueryBuilder();
        BoolQuery.Builder builder = new BoolQuery.Builder();
        // 2.如果查询条件有关键词，关键词可以匹配商品名、副标题、品牌字段，否则查询所有商品
        if (!StringUtils.hasText(goodsSearchParam.getKeyword())){
            MatchAllQuery matchAllQuery = new MatchAllQuery.Builder().build();
            builder.must(matchAllQuery._toQuery());
        }else {
            String keyword = goodsSearchParam.getKeyword();
            MultiMatchQuery multiMatchQuery = MultiMatchQuery.of(q -> q.query(keyword).fields("goodsName", "caption", "brand"));
            builder.must(multiMatchQuery._toQuery());
        }
        // 3.如果查询条件有品牌，精准查询品牌
        String brand = goodsSearchParam.getBrand();
        if (StringUtils.hasText(brand)){
            TermQuery termQuery = TermQuery.of(q -> q.field("brand").value(brand));
            builder.must(termQuery._toQuery());
        }
        // 4.如果查询条件有价格，则匹配价格
        Double highPrice = goodsSearchParam.getHighPrice();
        Double lowPrice = goodsSearchParam.getLowPrice();
        //最高价检索：得到最高价，检索比这个最高价低的数据
        if (highPrice != null && highPrice != 0){
            RangeQuery lte = RangeQuery.of(q -> q.field("price").lte(JsonData.of(highPrice)));
            builder.must(lte._toQuery());
        }
        //最低价检索：得到最低价，检索比这个最低价高的数据
        if (lowPrice != null && lowPrice != 0){
            RangeQuery gte = RangeQuery.of(q -> q.field("price").gte(JsonData.of(lowPrice)));
            builder.must(gte._toQuery());
        }
        // 5.如果查询条件有规格项，则精准匹配规格项
        Map<String, String> specificationOption = goodsSearchParam.getSpecificationOption();
        if (specificationOption != null && specificationOption.size() > 0){
            for (Map.Entry<String, String> stringStringEntry : specificationOption.entrySet()) {
                String key = stringStringEntry.getKey();
                String value = stringStringEntry.getValue();
                if (StringUtils.hasText(key)){
                    TermQuery termQuery = TermQuery.of(q -> q.field("specification." + key + ".keyword").value(value));
                    builder.must(termQuery._toQuery());
                }
            }
        }
        nativeQueryBuilder.withQuery(builder.build()._toQuery());
        // 6.添加分页条件
        PageRequest pageRequest = PageRequest.of(goodsSearchParam.getPage() - 1, goodsSearchParam.getSize());
        nativeQueryBuilder.withPageable(pageRequest);
        // 7.如果查询条件有排序，则添加排序条件
        String sort = goodsSearchParam.getSort();
        String sortFiled = goodsSearchParam.getSortFiled();
        if (StringUtils.hasText(sort) && StringUtils.hasText(sortFiled)){
            Sort sortParam = null;
            if (sortFiled.equals("NEW")){
                if (sort.equals("ASC")){
                    sortParam = Sort.by(Sort.Direction.DESC,"id");
                }
                if (sort.equals("DESC")){
                    sortParam = Sort.by(Sort.Direction.ASC,"id");
                }
            }
            if (sortFiled.equals("PRICE")){
                if (sort.equals("DESC")){
                    sortParam = Sort.by(Sort.Direction.DESC,"price");
                }
                if (sort.equals("ASC")){
                    sortParam = Sort.by(Sort.Direction.ASC,"price");
                }
            }
            nativeQueryBuilder.withSort(sortParam);
        }

        // 8.返回封装好的搜索条件
        return nativeQueryBuilder.build();
    }
    /**
     * 将商品信息同步到ES中
     * 将查询出来的商品详情GoodsDesc对象转化为GoodsES对象
     * @param goodsDesc 商品详情
     */
    @Override
    public void syncGoodToEs(GoodsDesc goodsDesc) {
        GoodsES goodsES = new GoodsES();
        goodsES.setId(goodsDesc.getId());
        goodsES.setGoodsName(goodsDesc.getGoodsName());
        goodsES.setCaption(goodsDesc.getCaption());
        goodsES.setPrice(goodsDesc.getPrice());
        goodsES.setHeaderPic(goodsDesc.getHeaderPic());
        goodsES.setBrand(goodsDesc.getBrand().getName());

        List<String> productTypes = new ArrayList<>();
        productTypes.add(goodsDesc.getProductType1().getName());
        productTypes.add(goodsDesc.getProductType2().getName());
        productTypes.add(goodsDesc.getProductType3().getName());
        goodsES.setProductType(productTypes);


        Map<String,List<String>> map = new HashMap<>();
        List<Specification> specifications = goodsDesc.getSpecifications();
        for (Specification specification : specifications) {
            String specName = specification.getSpecName();
            List<SpecificationOption> specificationOptions = specification.getSpecificationOptions();
            List<String> specificationOptionsName = new ArrayList<>();
            for (SpecificationOption specificationOption : specificationOptions) {
                specificationOptionsName.add(specificationOption.getOptionName());
            }
            map.put(specName,specificationOptionsName);
            goodsES.setSpecification(map);
        }

        List<String> tags = new ArrayList<>();
        tags.add(goodsDesc.getBrand().getName());
        tags.addAll(analyze(goodsDesc.getGoodsName(),"ik_pinyin"));
        tags.addAll(analyze(goodsDesc.getCaption(),"ik_pinyin"));
        goodsES.setTags(tags);

        goodsESRepository.save(goodsES);
    }

    /**
     * 删除ES中的商品数据
     */
    @Override
    public void delete(Long id) {
        goodsESRepository.deleteById(id);
    }
}
